#pragma once

#include <string>
#include <memory>
#include <functional>
#include <unordered_map>
#include "Epoller.hpp"
#include "Socket.hpp"
#include "Comm.hpp"

class Connection;
class TcpServer;

using func_t = function<void(std::weak_ptr<Connection>)>;
using excp_func_t = function<void(std::weak_ptr<Connection>)>;

class Connection
{
public:
    Connection(int sockfd, const std::weak_ptr<TcpServer> svr_ptr)
        : _sockfd(sockfd),
          _tcpsvr_ptr(svr_ptr)
    {
    }

    void SetHandler(func_t recv_cb, func_t send_cb, excp_func_t excp_cb)
    {
        _recv_cb = recv_cb;
        _send_cb = send_cb;
        _excp_cb = excp_cb;
    }

    void AppendInbuffer(const std::string &info)
    {
        _inbuf += info;
    }

    void AppendOutbuffer(const std::string &info)
    {
        _outbuf += info;
    }

    int Sockfd()
    {
        return _sockfd;
    }

    string &Inbuffer()
    {
        return _inbuf;
    }

    string &Outbuffer()
    {
        return _outbuf;
    }

    ~Connection()
    {
    }

private:
    int _sockfd;
    std::string _inbuf;
    std::string _outbuf;

public:
    func_t _recv_cb;
    func_t _send_cb;
    excp_func_t _excp_cb;

    std::weak_ptr<TcpServer> _tcpsvr_ptr;

    std::string _ip;
    uint16_t _port;
};

uint32_t EVENT_IN = (EPOLLIN | EPOLLET);
uint32_t EVENT_OUT = (EPOLLOUT | EPOLLET);

class TcpServer : public nocopy, public enable_shared_from_this<TcpServer>
{
private:
    static const int num = 64;
    static const int g_buffer_size = 128;

private:
    std::shared_ptr<Epoller> _epoller_ptr;
    std::shared_ptr<Sock> _listensock_ptr;
    std::unordered_map<int, std::shared_ptr<Connection>> _connections;
    struct epoll_event revs[num];
    uint16_t _port;
    bool _quit;

    func_t _OnMessage;

public:
    TcpServer(uint16_t port, func_t OnMessage)
        : _port(port),
          _epoller_ptr(new Epoller),
          _listensock_ptr(new Sock),
          _OnMessage(OnMessage)
    {
    }

    void Init()
    {
        _listensock_ptr->Socket();
        SetNonBlock(_listensock_ptr->Fd());
        _listensock_ptr->Bind(_port);
        _listensock_ptr->Listen();
        lg(Info, "listensock create successfully!");
        AddConnection(_listensock_ptr->Fd(), EVENT_IN,
                      std::bind(&TcpServer::Accepter, this, std::placeholders::_1), nullptr, nullptr);
    }

    void Loop()
    {
        _quit = false;
        while (!_quit)
        {
            Dispatcher(-1);
            // PrintConnection();
        }
        _quit = true;
    }

    void PrintConnection()
    {
        std::cout << "_connections fd list: ";
        for (auto &connection : _connections)
        {
            std::cout << connection.second->Sockfd() << ", ";
            std::cout << "inbuffer: " << connection.second->Inbuffer().c_str();
        }
        std::cout << std::endl;
    }

    ~TcpServer()
    {
    }

public:
    bool IsConnectionSafe(int sockfd)
    {
        auto iter = _connections.find(sockfd);
        if (iter == _connections.end())
            return false;
        return true;
    }

    void AddConnection(int sockfd, uint32_t event, func_t recv_cb, func_t send_cb, excp_func_t excp_cb,
                       const string &ip = "0.0.0.0", uint16_t port = 0)
    {
        // 1.首先创建出Connection对象
        std::shared_ptr<Connection> connection(new Connection(sockfd, shared_from_this()));
        connection->SetHandler(recv_cb, send_cb, excp_cb);
        connection->_ip = ip;
        connection->_port = port;
        // 2.然后将对应的sockfd加入进epoll内核中
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD, sockfd, event);
        // 3.最后将connection加入到映射中
        _connections[sockfd] = connection;

        lg(Info, "a connection create successfully! sockfd is : %d", sockfd);
    }

    void Dispatcher(int timeout)
    {
        int n = _epoller_ptr->EpollerWait(revs, num, timeout);
        for (int i = 0; i < n; ++i)
        {
            uint32_t events = revs[i].events;
            int sockfd = revs[i].data.fd;
            bool flag = IsConnectionSafe(sockfd);

            // 只需要处理读写事件，如果中途出现问题则直接在读写函数内进行处理
            if ((events & EPOLLIN) && flag)
                if (_connections[sockfd]->_recv_cb)
                    _connections[sockfd]->_recv_cb(_connections[sockfd]);
            if ((events & EPOLLOUT) && flag)
                if (_connections[sockfd]->_send_cb)
                    _connections[sockfd]->_send_cb(_connections[sockfd]);
        }
    }

    void Accepter(std::weak_ptr<Connection> conn)
    {
        auto connection = conn.lock();
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = ::accept(connection->Sockfd(), (struct sockaddr *)&peer, &len);
            if (sockfd > 0)
            {
                uint16_t peerport = ntohs(peer.sin_port);
                char ipbuf[128];
                inet_ntop(AF_INET, &(peer.sin_addr), ipbuf, sizeof(ipbuf));
                SetNonBlock(sockfd);
                AddConnection(sockfd, EVENT_IN,
                              std::bind(&TcpServer::Recver, this, std::placeholders::_1),
                              std::bind(&TcpServer::Sender, this, std::placeholders::_1),
                              std::bind(&TcpServer::Excepter, this, std::placeholders::_1),
                              ipbuf, peerport);
                lg(Info, "get a new client link, get info-> [%s:%d], sockfd : %d", ipbuf, peerport, sockfd);
            }
            else
            {
                // 事件不就绪，直接结束轮询
                if (errno == EWOULDBLOCK)
                    break;
                // 被信号中断，重新开始轮询
                else if (errno == EINTR)
                    continue;
                else
                    break;
            }
        }
    }

    void Recver(std::weak_ptr<Connection> conn)
    {
        auto connection = conn.lock();
        int sockfd = connection->Sockfd();
        while (true)
        {
            char buff[g_buffer_size];
            memset(buff, 0, sizeof(buff));
            ssize_t n = recv(sockfd, buff, sizeof(buff), 0);
            if (n > 0)
            {
                connection->AppendInbuffer(buff);
            }
            else if (n == 0)
            {
                lg(Info, "client info [%s:%d] close the link, me too.", connection->_ip.c_str(), connection->_port);
                connection->_excp_cb(connection);
                break;
            }
            else
            {
                if (errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR)
                    continue;
                else
                {
                    lg(Warning, "sockfd: %d, client info [%s:%d] recv error...", sockfd, connection->_ip, connection->_port);
                    connection->_excp_cb(connection);
                    break;
                }
            }
        }
        _OnMessage(connection);
    }

    void EnableEvent(int sockfd, bool readable, bool writeable)
    {
        uint32_t events;
        events |= (readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0) | EPOLLET;
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_MOD, sockfd, events);
    }

    void Sender(std::weak_ptr<Connection> conn)
    {
        auto connection = conn.lock();
        auto &outbuffer = connection->Outbuffer();
        int sockfd = connection->Sockfd();
        while (true)
        {
            ssize_t n = send(sockfd, outbuffer.c_str(), outbuffer.size(), 0);
            if (n > 0)
            {
                outbuffer.erase(0, n);
                if (outbuffer.empty())
                    break;
            }
            else if (n == 0)
            {
                return;
            }
            else
            {
                if (errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR)
                    break;
                else
                {
                    lg(Warning, "sockfd: %d, client info [%s:%d] recv error...", sockfd, connection->_ip, connection->_port);
                    connection->_excp_cb(connection);
                    break;
                }
            }
        }

        if(outbuffer.empty())
        {
            // 关闭对写事件的关心
            EnableEvent(sockfd, true, false);
        }
        else
        {
            // 开启对写事件的关心
            EnableEvent(sockfd, true, true);
        }
    }

    void Excepter(std::weak_ptr<Connection> conn)
    {
        auto connection = conn.lock();
        int sockfd = connection->Sockfd();
        if(!IsConnectionSafe(sockfd)) return;
        // 1.从epoll内核中删除当前链接的fd
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL, sockfd, 0);
        // 2.关闭当前文件描述符
        close(sockfd);
        lg(Debug, "sockfd : %d closed.", sockfd);
        // 3.从映射中删除当前sockfd对应的链接
        _connections.erase(sockfd);
        lg(Debug, "remove sockfd : %d, from unordered_map.", sockfd);
    }
};